'''
Created on Aug 3, 2010

@author: adolfo

Unit test suite for the implementation of SMSGateway for Gammu.
'''

from ..gammu_sms_gateway import GammuSMSGateway
from ..sms_gateway import Message, SMSListener
from sms_gateway_test import MessageRecordingListener
from sms_gateway_test_application_context import GammuSMSGatewayTestConfig
from springpython.context import ApplicationContext
from springpython.database.core import RowMapper
import copy
import logging
import socket
import unittest

_logger = logging.getLogger("mx.org.came.tests.gammu_sms_gateway_test")


class GammuSMSGatewayTest(unittest.TestCase):
    """Class that defines tests for the methods in the module
    `gammu_sms_gateway`"""

    CONTEXT = ApplicationContext(GammuSMSGatewayTestConfig())
    """Spring application context with the objects to be used in the
    unit test suite.
    """

    def setUp(self):
        """Initializes copies of the tables Gammu uses to store SMS messages
        in a test database."""
        self._create_tables()
        self._insert_data()
    
    def tearDown(self):
        """Removes the copies of the tables Gammu uses to store SMS messages
        from the test database."""
        self._drop_tables()
        
    
    def _create_tables(self):
        """Creates copies of the tables Gammu uses to store messages that
        have arrived ('inbox') and those that should be sent ('outbox').
        """
        CREATE_INBOX_SQL = """CREATE TABLE inbox (
                UpdatedInDB NUMERIC NOT NULL DEFAULT (datetime('now')),
                ReceivingDateTime NUMERIC NOT NULL DEFAULT (datetime('now')),
                Text TEXT NOT NULL,
                SenderNumber TEXT NOT NULL DEFAULT '',
                Coding TEXT NOT NULL DEFAULT 'Default_No_Compression',
                UDH TEXT NOT NULL,
                SMSCNumber TEXT NOT NULL DEFAULT '',
                Class INTEGER NOT NULL DEFAULT '-1',
                TextDecoded TEXT NOT NULL DEFAULT '',
                ID INTEGER PRIMARY KEY AUTOINCREMENT,
                Processed TEXT NOT NULL DEFAULT 'false',
                CHECK (Coding IN 
                ('Default_No_Compression','Unicode_No_Compression','8bit'
                 ,'Default_Compression','Unicode_Compression')) 
                );
        """
        CREATE_OUTBOX_SQL = """CREATE TABLE outbox (
                UpdatedInDB NUMERIC NOT NULL DEFAULT (datetime('now')),
                InsertIntoDB NUMERIC NOT NULL DEFAULT (datetime('now')),
                SendingDateTime NUMERIC NOT NULL DEFAULT (datetime('now')),
                Text TEXT,
                DestinationNumber TEXT NOT NULL DEFAULT '',
                Coding TEXT NOT NULL DEFAULT 'Default_No_Compression',
                UDH TEXT,
                Class INTEGER DEFAULT '-1',
                TextDecoded TEXT NOT NULL DEFAULT '',
                ID INTEGER PRIMARY KEY AUTOINCREMENT,
                MultiPart TEXT NOT NULL DEFAULT 'false',
                RelativeValidity INTEGER DEFAULT '-1',
                SenderID TEXT,
                SendingTimeOut NUMERIC NOT NULL DEFAULT (datetime('now')),
                DeliveryReport TEXT DEFAULT 'default',
                CreatorID TEXT NOT NULL,
                CHECK (Coding IN 
                ('Default_No_Compression','Unicode_No_Compression','8bit'
                 ,'Default_Compression','Unicode_Compression')),
                CHECK (DeliveryReport IN ('default','yes','no'))
                );
        """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")
        gammu_gateway._database_template.execute(CREATE_INBOX_SQL)
        gammu_gateway._database_template.execute(CREATE_OUTBOX_SQL)
        
    def _insert_data(self):
        """Insert test data into the tables of the test database."""
        INSERT_MESSAGE_SQL = """
                INSERT INTO inbox (
                    ID, Text, TextDecoded, SenderNumber, Coding, UDH)
                VALUES (%s, %s, %s, %s, %s, %s)
        """
        # Insert a text message
        database_template = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")._database_template
        message = GammuSMSGatewayTest.CONTEXT.get_object("text_message")
        database_template.update(INSERT_MESSAGE_SQL
                                 , (1
                                    , ""
                                    , message.data[Message.ORIGINAL_BINARY_MESSAGE]
                                    , message.data[Message.SENDER_PHONE_NUMBER]
                                    , "Default_No_Compression"
                                    , message.data[Message.UDH]))
        # Insert a binary message
        message = GammuSMSGatewayTest.CONTEXT.get_object("binary_message")
        database_template.update(INSERT_MESSAGE_SQL
                                 , (2
                                    , message.data[Message.ORIGINAL_BINARY_MESSAGE].encode("hex_codec")
                                    , ""
                                    , message.data[Message.SENDER_PHONE_NUMBER]
                                    , "8bit"
                                    , message.data[Message.UDH].encode("hex_codec")))
    
    def _drop_tables(self):
        """Removes the copies of the tables Gammu uses to store messages that
        have arrived ('inbox') and those that should be sent ('outbox').
        """
        DROP_INBOX_SQL = """DROP TABLE IF EXISTS inbox;"""
        DROP_OUTBOX_SQL = """DROP TABLE IF EXISTS outbox;"""
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")
        gammu_gateway._database_template.execute(DROP_OUTBOX_SQL)
        gammu_gateway._database_template.execute(DROP_INBOX_SQL)

    def test_init(self):
        """Tests that the initialization parameters are being handled properly
        """
        # Check that the default values are being properly initialized
        gammu_gateway = GammuSMSGateway(None)
        self.assertEquals(gammu_gateway._database_template.connection_factory
                          , None
                          , "Gammu's connection factory was not properly initialized")
        self.assertEquals(gammu_gateway._listeners, []
                          , "The list of listeners was not initialized to the empty list.")
        #Pylint reports an error in the following call by mistake    
        #pylint: disable=E1101
        self.assertEquals(gammu_gateway._notification_server.server_address
                          , ("127.0.0.1", 20000)
                          , "The notification server address was not initialized to 'localhost'.")
        self.assertEquals(gammu_gateway._gammu_phone_number, 1234
                          , "Gammu's phone number was not initialized to 1234")
        self.assertEquals(gammu_gateway._gammu_message_retrieval_query
                          , GammuSMSGateway.MESSAGE_RETRIEVAL_QUERY_SQL
                          , "Gammu's message retrieval query was not initialized to the default")
        self.assertEquals(gammu_gateway._gammu_message_delivery_query
                          , GammuSMSGateway.MESSAGE_DELIVERY_QUERY_SQL
                          , "Gammu's message delivery query was not initialized to the default")
        # Check that the values in the test application context were correctly initialized
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object("gammu_sms_gateway")
        self.assertEquals(gammu_gateway._database_template.connection_factory
                          , GammuSMSGatewayTest.CONTEXT.get_object("gammu_connection_factory")
                          , "Gammu's connection factory was not properly initialized")
        self.assertEquals(gammu_gateway._listeners, []
                          , "The list of listeners was not initialized to the empty list.")
        self.assertEquals(gammu_gateway._notification_server.server_address
                          , ("127.0.0.1", 22222)
                          , "The notification server address was not initialized to '127.0.0.1'.")
        self.assertEquals(gammu_gateway._gammu_phone_number, 2381280891
                          , "Gammu's phone number was not initialized to 2381280891")
        self.assertEquals(gammu_gateway._gammu_message_retrieval_query
                          , GammuSMSGatewayTestConfig.GAMMU_MESSAGE_RETRIEVAL_QUERY_SQL
                          , "Gammu's message retrieval query was not initialized to the default")
        self.assertEquals(gammu_gateway._gammu_message_delivery_query
                          , GammuSMSGatewayTestConfig.GAMMU_MESSAGE_DELIVERY_QUERY_SQL
                          , "Gammu's message delivery query was not initialized to the default")
        
    def test_get_phone_number(self):
        """Verifies that get_phone_number() returns the value it was 
        initialized to.
        """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object("gammu_sms_gateway")
        self.assertEquals(gammu_gateway.get_phone_number(), 2381280891
                          , "get_phone_number() did not return the expected value")
    
    def test_add_sms_listener(self):
        """Verifies that add_sms_listener(listener) actually adds a listener to
        the list.
        """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object("gammu_sms_gateway")
        sms_listener = SMSListener()
        gammu_gateway.add_sms_listener(sms_listener)
        self.assertTrue(sms_listener in gammu_gateway._listeners
                          , "add_sms_listener() did not register the listener")
        gammu_gateway.remove_sms_listener(sms_listener)
        self.assertTrue(sms_listener not in gammu_gateway._listeners
                          , "remove_sms_listener() did not remove the listener.")
    
    def test_notify_message_reception(self):
        """Verifies that registered listeners are notified by
         _notify_message_reception().
         """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")
        listener = MessageRecordingListener()
        message = Message()
        gammu_gateway.add_sms_listener(listener)
        # Check notification of a text message
        self.assertEquals(len(listener.received_messages), 0
                          , "Listener was expected to have not received any messages.")
        gammu_gateway._notify_message_reception(Message())
        self.assertEquals(len(listener.received_messages), 1
                          , "_notify_message_reception() is not reporting once.")
        self.assertEquals(listener.received_messages[0].data, message.data
                          , "Listener did not receive the expected message.")

    def test_get_message_from_db(self):
        """Verifies that text and binary messages stored in the database are
        being read correctly in instances of Message.
        """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object("gammu_sms_gateway")
        # Retrieve a text message
        expected_text_message = GammuSMSGatewayTest.CONTEXT.get_object("text_message")
        text_message = gammu_gateway._get_message_from_db(1)
        self.assertEquals(text_message.data, expected_text_message.data
                          , "Did not retrieve the text message correctly from the DB.")
        # Retrieve a binary message
        expected_binary_message = GammuSMSGatewayTest.CONTEXT.get_object("binary_message")
        binary_message = gammu_gateway._get_message_from_db(2)
        self.assertEquals(binary_message.data, expected_binary_message.data
                          , "Did not retrieve the binary message correctly from the DB.")
        #Exception raised when retrieving message that does not exist
        self.assertRaises(IndexError
                          , gammu_gateway._get_message_from_db, (1234))

    def test_start_service(self):
        """Verifies that the GammuSMSGateway service starts and stops on request.
        """
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object("gammu_sms_gateway")
        gammu_gateway.start_service()
        # Notify SMSGateway of the arrival of a message as Kannel would
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.sendto("1", gammu_gateway._notification_server.server_address)
        sock.close()
        # No verification of the arrival of the message is made since SQLite
        # cannot work with multiple threads (i.e. the message is not read.
        # But if no exception was raised by the socket, we were able to notify.
        _logger.info("Stopping GammuSMSGateway. If it does not work, the process will hang.")
        gammu_gateway.stop_service()
        _logger.info("GammuSMSGateway stopped")

    def _get_message_from_outbox(self):
        """Retrieves a message from the outbox table"""
        MESSAGE_RETRIEVAL_QUERY_SQL = """
            SELECT DestinationNumber, UDH, Text, TextDecoded, Coding
            FROM outbox
            """
        class OutboxMessageMapper(RowMapper):
            """Wraps the entries in Gammu's outbox table in instances of
             Message.
             """
            owner = self
            def map_row(self, row, metadata=None):
                """Maps a row from a query to Gammu's outbox table to an
                instance of Message.
                """
                message = Message()
                message.data[Message.RECEIVER_PHONE_NUMBER] = row[0]
                message.data[Message.UDH] = row[1].decode("hex_codec")
                # Set ORIGINAL_BINARY_MESSAGE to the binary message if the UDH is not
                # empty and to the decoded text if it is.
                message.data[Message.ORIGINAL_BINARY_MESSAGE] = \
                        row[2].decode("hex_codec") if row[1] else row[3]
                if row[1]:
                    self.owner.assertEquals(row[4], "8bit"
                            , "Binary message does not specify 8b coding.")
                else:
                    self.owner.assertEquals(row[4], "Default_No_Compression"
                            , "Text message does not specify 'Default_No_Compression' coding.")
                return message
        database_template = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")._database_template
        results = database_template.query(MESSAGE_RETRIEVAL_QUERY_SQL
                                                , ()
                                                , OutboxMessageMapper())
        return results

    def test_send_message(self):
        """Verifies that GammuSMSGateway sends messages correctly (stores a
        tuple in the outbox table.
        """
        # Verify the outbox is empty
        results = self._get_message_from_outbox()
        self.assertEquals(len(results), 0
                , "Outbox expected to be empty. No messages have been sent.")
        # Send a text and a binary message
        gammu_gateway = GammuSMSGatewayTest.CONTEXT.get_object(
                "gammu_sms_gateway")
        # First the text message.
        message = copy.deepcopy(GammuSMSGatewayTest.CONTEXT.get_object(
                "text_message"))
        message.data[Message.RECEIVER_PHONE_NUMBER] = "1234"
        message.data[Message.SENDER_PHONE_NUMBER] = None
        gammu_gateway.send_message(message)
        results = self._get_message_from_outbox()
        self.assertEquals(len(results), 1
                          , "Outbox expected to contain 1 message.")
        self.assertEquals(results[0].data, message.data
                , "The text message was not stored correctly for sending.")
        # Now the binary message.
        self.tearDown() # Clean the test database contents
        self.setUp()    # Set up the initial database contents
        message = copy.deepcopy(GammuSMSGatewayTest.CONTEXT.get_object(
                "binary_message"))
        message.data[Message.RECEIVER_PHONE_NUMBER] = "1234"
        message.data[Message.SENDER_PHONE_NUMBER] = None
        gammu_gateway.send_message(message)
        results = self._get_message_from_outbox()
        self.assertEquals(len(results), 1
                , "Outbox expected to contain 1 message.")
        self.assertEquals(results[0].data, message.data
                , "The binary message was not stored correctly for sending.")


if __name__ == '__main__':
    unittest.main()
